package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	_ "github.com/ibmdb/go_ibm_db"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"mydata/cmd/other"
	"mydata/internal/dbinfo"
	"mydata/internal/version"
)

var genSchemaCmd = &cobra.Command{
	Use:     "gen",
	Aliases: []string{"gen"},
	Short:   "gen",
	RunE:    RunGenSchemaE,
}

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Show version number",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println(version.GetRawInfo())
	},
}

var dbc = dbinfo.DbConf{}
var schema string
var table string
var fileName string

var debug bool

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)

	genSchemaCmd.Flags().StringVarP(&dbc.Addr, "addr", "a", "", "mysql database addr, format: ip:port")
	genSchemaCmd.Flags().StringVarP(&dbc.Username, "username", "u", "", "username for connect database")
	genSchemaCmd.Flags().StringVarP(&dbc.Password, "password", "p", "", "password for connect database")
	genSchemaCmd.Flags().StringVarP(&dbc.Dbname, "dbname", "D", "", "default database name")
	genSchemaCmd.Flags().StringVarP(&schema, "schema", "S", "", "schema name")
	genSchemaCmd.Flags().StringVarP(&table, "table", "T", "", "table name")
	genSchemaCmd.Flags().StringVarP(&fileName, "file-name", "o", "", "output filename")

	genSchemaCmd.Flags().BoolVar(&debug, "debug", false, "print debug log")

	genSchemaCmd.Flags().SortFlags = false // 禁止flag排序

	genSchemaCmd.AddCommand(versionCmd)
}

func ExecuteGenSchema() {
	genSchemaCmd.SilenceUsage = true

	if err := genSchemaCmd.Execute(); err != nil {
		if debug {
			fmt.Printf("%+v\n", err)
		}
		os.Exit(1)
	}
}

func RunGenSchemaE(*cobra.Command, []string) error {
	version.LogVersionInfo()

	conn, err := NewDB2(dbc)
	if err != nil {
		return err
	}
	defer conn.Close()

	db2Schema, err := GetSchemaFromDB2(conn, schema, table)
	if err != nil {
		return err
	}

	dst, err := os.Create(fileName)
	if err != nil {
		return errors.WithStack(err)
	}
	defer dst.Close()

	rowSize := 0
	for _, v := range db2Schema {
		if debug {
			log.Printf("%+v", v)
		}
		marshal, err := json.Marshal(&v)
		if err != nil {
			return errors.WithStack(err)
		}
		if _, err := dst.Write(marshal); err != nil {
			return errors.WithStack(err)
		}
		dst.WriteString("\n")

		rowSize += v.RealLen
	}

	log.Printf(">>> row size:%d", rowSize)

	return nil
}

//var reg = regexp.MustCompile("[!-~]")

func NewDB2(conf dbinfo.DbConf) (*sql.DB, error) {
	splits := strings.SplitN(conf.Addr, ":", 2)
	if len(splits) != 2 {
		return nil, errors.Errorf("database addr invalid, format ip:port, but got %s", conf.Addr)
	}

	dsn := fmt.Sprintf("UID=%s;PWD=%s;HOSTNAME=%s;PORT=%s;DATABASE=%s;StmtConcentrator=WITHLITERALS;", conf.Username, conf.Password, splits[0], splits[1], conf.Dbname)
	log.Printf("dsn:%s", dsn)

	conn, err := sql.Open("go_ibm_db", dsn)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if err := conn.Ping(); err != nil {
		return nil, errors.Wrapf(err, "ping db failed:%s", dsn)
	}

	return conn, nil
}

func GetSchemaFromDB2(db *sql.DB, schema, table string) ([]other.Col, error) {
	cols := make([]other.Col, 0)
	s := fmt.Sprintf("select colno, name, coltype, length, scale, nulls from sysibm.syscolumns where tbcreator='%s' and tbname = '%s' order by colno asc", schema, table)
	rows, err := db.Query(s)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	defer rows.Close()

	for rows.Next() {
		var c other.Col
		if err := rows.Scan(&c.ColNO, &c.Name, &c.ColType, &c.Length, &c.Scale, &c.Nulls); err != nil {
			return nil, errors.WithStack(err)
		}

		// "CHAR", "CHARACTER", "DATE", "TIME", "TIMESTMP", "VARCHAR", "DECIMAL", "INT", "INTEGER", "SMALLINT"
		c.ColType = strings.Trim(c.ColType, " ") // trim
		switch c.ColType {
		case "DECIMAL":
			c.RealLen = c.Length/2 + 1
		case "DATE":
			c.RealLen = 10
		case "TIME":
			c.RealLen = 8
		case "TIMESTMP":
			c.RealLen = 26
		case "VARCHAR":
			c.RealLen = c.Length + 2 // varchar plug 2 byte
		case "CHAR", "CHARACTER", "SMALLINT", "INT", "INTEGER":
			c.RealLen = c.Length
		default:
			return nil, errors.Errorf("not support data type, col:%s, type:%s", c.Name, c.ColType)
		}

		// ReadLen包含null标记的1个字节
		if c.Nulls == "Y" {
			c.RealLen++
		}

		cols = append(cols, c)
	}

	if rows.Err() != nil {
		return nil, errors.WithStack(err)
	}

	return cols, nil
}

func main() {
	ExecuteGenSchema()
}
